-module('rebar.config').

-export([do/2]).

do(Dir, CONFIG) ->
    ok = assert_otp(),
    case iolist_to_binary(Dir) of
        <<".">> ->
            C1 = deps(CONFIG),
            Config = dialyzer(C1),
            maybe_dump(Config ++ [{overrides, overrides()}] ++ coveralls() ++ config());
        _ ->
            CONFIG
    end.

assert_otp() ->
    Oldest = 23,
    Latest = 24,
    OtpRelease = list_to_integer(erlang:system_info(otp_release)),
    case OtpRelease < Oldest orelse OtpRelease > Latest of
        true ->
            io:format(standard_error, "ERROR: Erlang/OTP version ~p found. min=~p, recommended=~p~n",
                      [OtpRelease, Oldest, Latest]),
            halt(1);
        false when OtpRelease =/= Latest ->
            io:format("WARNING: Erlang/OTP version ~p found, recommended==~p~n",
                      [OtpRelease, Latest]);
        false ->
            ok
    end.

bcrypt() ->
    {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {branch, "0.6.0"}}}.

quicer() ->
    {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.9"}}}.

deps(Config) ->
    {deps, OldDeps} = lists:keyfind(deps, 1, Config),
    MoreDeps = [bcrypt() || provide_bcrypt_dep()] ++
        [quicer() || is_quicer_supported()],
    lists:keystore(deps, 1, Config, {deps, OldDeps ++ MoreDeps}).

overrides() ->
    [ {add, [ {extra_src_dirs, [{"etc", [{recursive, true}]}]}
            ]}
    ] ++ snabbkaffe_overrides().

%% Temporary workaround for a rebar3 erl_opts duplication
%% bug. Ideally, we want to set this define globally
snabbkaffe_overrides() ->
    Apps = [snabbkaffe, ekka, mria],
    [{add, App, [{erl_opts, [{d, snk_kind, msg}]}]} || App <- Apps].

config() ->
    [ {cover_enabled, is_cover_enabled()}
    , {profiles, profiles()}
    , {plugins, plugins()}
    ].

is_cover_enabled() ->
    case os:getenv("ENABLE_COVER_COMPILE") of
        "1"-> true;
        "true" -> true;
        _ -> false
    end.

is_enterprise(ce) -> false;
is_enterprise(ee) -> true.

is_quicer_supported() ->
    not (false =/= os:getenv("BUILD_WITHOUT_QUIC") orelse
         is_win32() orelse is_centos_6()
        ).

is_centos_6() ->
    %% reason:
    %% glibc is too old
    case file:read_file("/etc/centos-release") of
        {ok, <<"CentOS release 6", _/binary >>} ->
            true;
        _ ->
            false
    end.

is_win32() ->
    win32 =:= element(1, os:type()).

project_app_dirs(Edition) ->
    ["apps/*"] ++
    case is_enterprise(Edition) of
        true -> ["lib-ee/*"];
        false -> []
    end.

plugins() ->
    [ {relup_helper,{git,"https://github.com/emqx/relup_helper", {tag, "2.0.0"}}}
    , {er_coap_client, {git, "https://github.com/emqx/er_coap_client", {tag, "v1.0.4"}}}
      %% emqx main project does not require port-compiler
      %% pin at root level for deterministic
    , {pc, {git, "https://github.com/emqx/port_compiler.git", {tag, "v1.11.1"}}}
    ]
    %% test plugins are concatenated to default profile plugins
    %% otherwise rebar3 test profile runs are super slow
    ++ test_plugins().

test_plugins() ->
    [ {rebar3_proper, "0.12.1"}
    , {coveralls, {git, "https://github.com/emqx/coveralls-erl", {tag, "v2.2.0-emqx-1"}}}
    ].

test_deps() ->
    [ {bbmustache, "1.10.0"}
    , {meck, "0.9.2"}
    , {proper, "1.4.0"}
    ].

common_compile_opts(Vsn) ->
    [ debug_info % always include debug_info
    , {compile_info, [{emqx_vsn, Vsn}]}
    ] ++
    [{d, 'EMQX_BENCHMARK'} || os:getenv("EMQX_BENCHMARK") =:= "1" ].

prod_compile_opts(Vsn) ->
    [ compressed
    , deterministic
    , warnings_as_errors
    | common_compile_opts(Vsn)
    ].

prod_overrides() ->
    [{add, [ {erl_opts, [deterministic]}]}].

profiles() ->
    profiles_ce() ++ profiles_ee() ++ profiles_dev().

profiles_ce() ->
    Vsn = get_vsn(emqx),
    [ {'emqx',
       [ {erl_opts, prod_compile_opts(Vsn)}
       , {relx, relx(Vsn, cloud, bin, ce)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ce)}
       , {post_hooks, [{compile, "bash build emqx doc"}]}
       ]}
    , {'emqx-pkg',
       [ {erl_opts, prod_compile_opts(Vsn)}
       , {relx, relx(Vsn, cloud, pkg, ce)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ce)}
       , {post_hooks, [{compile, "bash build emqx-pkg doc"}]}
       ]}
    , {'emqx-edge',
       [ {erl_opts, prod_compile_opts(Vsn)}
       , {relx, relx(Vsn, edge, bin, ce)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ce)}
       , {post_hooks, [{compile, "bash build emqx-edge doc"}]}
       ]}
    , {'emqx-edge-pkg',
       [ {erl_opts, prod_compile_opts(Vsn)}
       , {relx, relx(Vsn, edge, pkg, ce)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ce)}
       , {post_hooks, [{compile, "bash build emqx-edge-pkg doc"}]}
       ]}
    ].

profiles_ee() ->
    Vsn = get_vsn('emqx-enterprise'),
    EE = {d, 'EMQX_ENTERPRISE'},
    [ {'emqx-enterprise',
       [ {erl_opts, [EE | prod_compile_opts(Vsn)]}
       , {relx, relx(Vsn, cloud, bin, ee)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ee)}
       , {post_hooks, [{compile, "bash build emqx-enterprise doc"}]}
       ]}
    , {'emqx-enterprise-pkg',
       [ {erl_opts, [EE | prod_compile_opts(Vsn)]}
       , {relx, relx(Vsn, cloud, pkg, ee)}
       , {overrides, prod_overrides()}
       , {project_app_dirs, project_app_dirs(ee)}
       , {post_hooks, [{compile, "bash build emqx-enterprise-pkg doc"}]}
       ]}
    ].

%% EE has more files than CE, always test/check with EE options.
profiles_dev() ->
    Vsn = get_vsn('emqx-enterprise'),
    EE = {d, 'EMQX_ENTERPRISE'},
    [ {check,
       [ {erl_opts, [EE | common_compile_opts(Vsn)]}
       , {project_app_dirs, project_app_dirs(ee)}
       ]}
    , {test,
       [ {deps, test_deps()}
       , {erl_opts, [EE | common_compile_opts(Vsn) ++ erl_opts_i()]}
       , {extra_src_dirs, [{"test", [{recursive, true}]}]}
       , {project_app_dirs, project_app_dirs(ee)}
       ]}
    ].

%% RelType: cloud (full size) | edge (slim size)
%% PkgType: bin | pkg
%% Edition: ce (community) | ee (enterprise)
relx(Vsn, RelType, PkgType, Edition) ->
    [ {include_src,false}
    , {include_erts, true}
    , {extended_start_script,false}
    , {generate_start_script,false}
    , {sys_config,false}
    , {vm_args,false}
    , {release, {emqx, Vsn}, relx_apps(RelType, Edition)}
    , {overlay, relx_overlay(RelType, Edition)}
    , {overlay_vars, [ {built_on_arch, rebar_utils:get_arch()}
                     , {emqx_description, emqx_description(RelType, Edition)}
                     | overlay_vars(RelType, PkgType, Edition)]}
    ].

emqx_description(cloud, ee) -> "EMQ X Enterprise Edition";
emqx_description(cloud, ce) -> "EMQ X Community Edition";
emqx_description(edge, ce)  -> "EMQ X Edge Edition".

overlay_vars(RelType, PkgType, Edition) ->
    overlay_vars_rel(RelType)
    ++ overlay_vars_pkg(PkgType)
    ++ overlay_vars_edition(Edition).

%% vars per release type, cloud or edge
overlay_vars_rel(RelType) ->
    VmArgs = case RelType of
                 cloud -> "vm.args";
                 edge -> "vm.args.edge"
             end,

    [ {vm_args_file, VmArgs}
    ].

overlay_vars_edition(ce) ->
    [ {emqx_schema_mod, emqx_conf_schema}
    , {is_enterprise, "no"}
    , {emqx_machine_boot_apps, emqx_machine_boot_app_list(ce)}
    ];
overlay_vars_edition(ee) ->
    [ {emqx_schema_mod, emqx_enterprise_conf_schema}
    , {is_enterprise, "yes"}
    , {emqx_machine_boot_apps, emqx_machine_boot_app_list(ee)}
    ].

%% vars per packaging type, bin(zip/tar.gz/docker) or pkg(rpm/deb)
overlay_vars_pkg(bin) ->
    [ {platform_bin_dir, "bin"}
    , {platform_data_dir, "data"}
    , {platform_etc_dir, "etc"}
    , {platform_lib_dir, "lib"}
    , {platform_log_dir, "log"}
    , {platform_plugins_dir, "plugins"}
    , {runner_root_dir, "$(cd $(dirname $(readlink $0 || echo $0))/..; pwd -P)"}
    , {runner_bin_dir, "$RUNNER_ROOT_DIR/bin"}
    , {runner_etc_dir, "$RUNNER_ROOT_DIR/etc"}
    , {runner_lib_dir, "$RUNNER_ROOT_DIR/lib"}
    , {runner_log_dir, "$RUNNER_ROOT_DIR/log"}
    , {runner_data_dir, "$RUNNER_ROOT_DIR/data"}
    , {runner_user, ""}
    , {is_elixir, "no"}
    ];
overlay_vars_pkg(pkg) ->
    [ {platform_bin_dir, ""}
    , {platform_data_dir, "/var/lib/emqx"}
    , {platform_etc_dir, "/etc/emqx"}
    , {platform_lib_dir, ""}
    , {platform_log_dir, "/var/log/emqx"}
    , {platform_plugins_dir, "/var/lib/emqx/plugins"}
    , {runner_root_dir, "/usr/lib/emqx"}
    , {runner_bin_dir, "/usr/bin"}
    , {runner_etc_dir, "/etc/emqx"}
    , {runner_lib_dir, "$RUNNER_ROOT_DIR/lib"}
    , {runner_log_dir, "/var/log/emqx"}
    , {runner_data_dir, "/var/lib/emqx"}
    , {runner_user, "emqx"}
    , {is_elixir, "no"}
    ].

relx_apps(ReleaseType, Edition) ->
    [ kernel
    , sasl
    , crypto
    , public_key
    , asn1
    , syntax_tools
    , ssl
    , os_mon
    , inets
    , compiler
    , runtime_tools
    , redbug
    , {hocon, load}
    , {emqx, load} % started by emqx_machine
    , {emqx_conf, load}
    , emqx_machine
    , {mnesia, load}
    , {ekka, load}
    , {emqx_plugin_libs, load}
    , {esasl, load}
    , observer_cli
    , {system_monitor, load} % started by emqx_machine
    , emqx_http_lib
    , emqx_resource
    , emqx_connector
    , emqx_authn
    , emqx_authz
    , emqx_auto_subscribe
    , emqx_gateway
    , emqx_exhook
    , emqx_bridge
    , emqx_rule_engine
    , emqx_modules
    , emqx_management
    , emqx_dashboard
    , emqx_retainer
    , emqx_statsd
    , emqx_prometheus
    , emqx_psk
    , emqx_slow_subs
    , emqx_plugins
    ]
    ++ [quicer || is_quicer_supported()]
    ++ [bcrypt || provide_bcrypt_release(ReleaseType)]
    ++ relx_apps_per_rel(ReleaseType)
    ++ relx_additional_apps(ReleaseType, Edition).

relx_apps_per_rel(cloud) ->
    [ xmerl
    | [{observer, load} || is_app(observer)]
    ];
relx_apps_per_rel(edge) ->
    [].

is_app(Name) ->
    case application:load(Name) of
        ok -> true;
        {error,{already_loaded, _}} -> true;
        _ -> false
    end.

relx_additional_apps(ReleaseType, Edition) ->
    relx_plugin_apps_per_rel(ReleaseType)
    ++ relx_apps_per_edition(Edition).

relx_plugin_apps_per_rel(cloud) ->
    [];
relx_plugin_apps_per_rel(edge) ->
    [].

relx_apps_per_edition(ee) ->
    [ emqx_license
    , {emqx_enterprise_conf, load}
    ];

relx_apps_per_edition(ce) -> [].

emqx_machine_boot_apps(ce) ->
    [ emqx_prometheus
    , emqx_modules
    , emqx_dashboard
    , emqx_connector
    , emqx_gateway
    , emqx_statsd
    , emqx_resource
    , emqx_rule_engine
    , emqx_bridge
    , emqx_plugin_libs
    , emqx_management
    , emqx_retainer
    , emqx_exhook
    , emqx_authn
    , emqx_authz
    , emqx_plugin
    ];

emqx_machine_boot_apps(ee) ->
    emqx_machine_boot_apps(ce) ++
    [].

emqx_machine_boot_app_list(Edition) ->
    string:join(
      [atom_to_list(AppName) || AppName <- emqx_machine_boot_apps(Edition)],
      ", ").

relx_overlay(ReleaseType, Edition) ->
    [ {mkdir, "log/"}
    , {mkdir, "data/"}
    , {mkdir, "plugins"}
    , {mkdir, "data/mnesia"}
    , {mkdir, "data/configs"}
    , {mkdir, "data/patches"}
    , {mkdir, "data/scripts"}
    , {template, "rel/emqx_vars", "releases/emqx_vars"}
    , {template, "rel/BUILT_ON", "releases/{{release_version}}/BUILT_ON"}
    , {copy, "bin/emqx", "bin/emqx"}
    , {copy, "bin/emqx_ctl", "bin/emqx_ctl"}
    , {copy, "bin/node_dump", "bin/node_dump"}
    , {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript"}
    , {copy, "bin/emqx", "bin/emqx-{{release_version}}"} %% for relup
    , {copy, "bin/emqx_ctl", "bin/emqx_ctl-{{release_version}}"} %% for relup
    , {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript-{{release_version}}"} %% for relup
    , {copy, "apps/emqx_gateway/src/lwm2m/lwm2m_xml", "etc/lwm2m_xml"}
    , {copy, "apps/emqx_authz/etc/acl.conf", "etc/acl.conf"}
    , {template, "bin/emqx.cmd", "bin/emqx.cmd"}
    , {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"}
    , {copy, "bin/nodetool", "bin/nodetool"}
    , {copy, "bin/nodetool", "bin/nodetool-{{release_version}}"}
    ] ++ etc_overlay(ReleaseType, Edition).

etc_overlay(ReleaseType, Edition) ->
    Templates = emqx_etc_overlay(ReleaseType, Edition),
    [ {mkdir, "etc/"}
    , {copy, "{{base_dir}}/lib/emqx/etc/certs","etc/"}
    ] ++
    lists:map(
      fun({From, To}) -> {template, From, To};
         (FromTo)     -> {template, FromTo, FromTo}
      end, Templates).

emqx_etc_overlay(ReleaseType, Edition) ->
    emqx_etc_overlay_per_rel(ReleaseType)
    ++ emqx_etc_overlay_per_edition(Edition)
    ++ emqx_etc_overlay_common().

emqx_etc_overlay_per_rel(cloud) ->
    [ {"{{base_dir}}/lib/emqx/etc/emqx_cloud/vm.args","etc/vm.args"}
    ];
emqx_etc_overlay_per_rel(edge) ->
    [ {"{{base_dir}}/lib/emqx/etc/emqx_edge/vm.args","etc/vm.args"}
    ].

emqx_etc_overlay_common() ->
    [ {"{{base_dir}}/lib/emqx/etc/ssl_dist.conf", "etc/ssl_dist.conf"}
    ].

emqx_etc_overlay_per_edition(ce) ->
    [ {"{{base_dir}}/lib/emqx_conf/etc/emqx.conf.all", "etc/emqx.conf"}
    ];
emqx_etc_overlay_per_edition(ee) ->
    [ {"{{base_dir}}/lib/emqx_conf/etc/emqx_enterprise.conf.all", "etc/emqx_enterprise.conf"}
    , {"{{base_dir}}/lib/emqx_conf/etc/emqx.conf.all", "etc/emqx.conf"}
    ].

get_vsn(Profile) ->
    %% to make it compatible to Linux and Windows,
    %% we must use bash to execute the bash file
    %% because "./" will not be recognized as an internal or external command
    PkgVsn = os:cmd("bash pkg-vsn.sh " ++ atom_to_list(Profile)),
    re:replace(PkgVsn, "\n", "", [{return ,list}]).

maybe_dump(Config) ->
    is_debug() andalso file:write_file("rebar.config.rendered", [io_lib:format("~p.\n", [I]) || I <- Config]),
    Config.

is_debug() -> is_debug("DEBUG") orelse is_debug("DIAGNOSTIC").

is_debug(VarName) ->
    case os:getenv(VarName) of
        false -> false;
        "" -> false;
        _ -> true
    end.

provide_bcrypt_dep() ->
    not is_win32().

provide_bcrypt_release(ReleaseType) ->
    provide_bcrypt_dep() andalso ReleaseType =:= cloud.

erl_opts_i() ->
    [{i, "apps"}] ++
    [{i, Dir}  || Dir <- filelib:wildcard(filename:join(["apps", "*", "include"]))] ++
    [{i, Dir}  || Dir <- filelib:wildcard(filename:join(["lib-ee", "*", "include"]))].

dialyzer(Config) ->
    {dialyzer, OldDialyzerConfig} = lists:keyfind(dialyzer, 1, Config),

    AppsToAnalyse = case os:getenv("DIALYZER_ANALYSE_APP") of
        false ->
            [];
        Value ->
            [ list_to_atom(App) || App <- string:tokens(Value, ",")]
    end,

    AppNames = app_names(),

    KnownApps = [Name ||  Name <- AppsToAnalyse, lists:member(Name, AppNames)],

    AppsToExclude = AppNames -- KnownApps,

    case length(AppsToAnalyse) > 0 of
        true ->
            lists:keystore(dialyzer, 1, Config, {dialyzer, OldDialyzerConfig ++ [{exclude_apps, AppsToExclude}]});
        false ->
            Config
    end.

coveralls() ->
    case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of
      {"true", Token} when is_list(Token) ->
        Cfgs = [{coveralls_repo_token, Token},
                {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")},
                {coveralls_commit_sha, os:getenv("GITHUB_SHA")},
                {coveralls_coverdata, "_build/test/cover/*.coverdata"},
                {coveralls_service_name, "github"}],
        case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request"
            andalso string:tokens(os:getenv("GITHUB_REF"), "/") of
            [_, "pull", PRNO, _] ->
                [{coveralls_service_pull_request, PRNO} | Cfgs];
            _ ->
                Cfgs
        end;
      _ ->
        []
    end.

app_names() -> list_dir("apps") ++ list_dir("lib-ee").

list_dir(Dir) ->
    case filelib:is_dir(Dir) of
        true ->
            {ok, Names} = file:list_dir(Dir),
            [list_to_atom(Name) || Name <- Names, filelib:is_dir(filename:join([Dir, Name]))];
        false ->
            []
    end.
